home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 011 / tsrsrc22.arc / RELEASE.PAS < prev    next >
Pascal/Delphi Source File  |  1987-03-05  |  29KB  |  824 lines

  1. {**************************************************************************
  2. *   Releases memory above the last MARK call made.                        *
  3. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  4. *   Released to the public domain for personal, non-commercial use only.  *
  5. ***************************************************************************
  6. *   Version 1.0 2/8/86                                                    *
  7. *     original public release.                                            *
  8. *     (thanks to Neil Rubenking for an outline of the method used)        *
  9. *   Version 1.1 2/11/86                                                   *
  10. *     fixed problem with processes which deallocate their environment.    *
  11. *   Version 1.2 2/13/86                                                   *
  12. *     fixed another problem with processes which deallocate environment.  *
  13. *   Version 1.3 2/15/86                                                   *
  14. *     added support for "named" marks.                                    *
  15. *   Version 1.4 2/23/86                                                   *
  16. *     added support for releasing programs which use Expanded Memory.     *
  17. *   Version 1.5 2/28/86                                                   *
  18. *     added more bulletproof method of finding first allocation block.    *
  19. *   Version 1.6 3/20/86                                                   *
  20. *     restore all FF interrupts.                                          *
  21. *     restore the termination address to the local process.               *
  22. *     reduce number of EMS blocks to 32.                                  *
  23. *     fix bug in number of EMS handles in EMS release step.               *
  24. *     restore an undocumented address in the PSP which allows RELEASE of  *
  25. *       a COMMAND shell (emulates the EXIT command).                      *
  26. *   Version 1.7 (date not recorded)                                       *
  27. *     add "protected" marks.                                              *
  28. *   Version 1.8 4/21/86                                                   *
  29. *     fix problem when mark is installed as 'MARK '.                      *
  30. *   Version 1.9 5/22/86                                                   *
  31. *     release the environment of MARK when it is not contiguous with      *
  32. *       the MARK itself.                                                  *
  33. *     capture RELEASE calls from within batch files and don't release the *
  34. *       batch control block.                                              *
  35. *     fiddle with different methods of restoring interrupt vectors in     *
  36. *       an attempt to successfully remove DoubleDos. No success, not      *
  37. *       implemented. Note, after more effort: DDos apparently             *
  38. *       reprograms the 8259 as well as patching the operating system.     *
  39. *   Version 2.0 6/17/86                                                   *
  40. *     support "file" marks placed by the new program FMARK.               *
  41. *   Version 2.1 7/18/86                                                   *
  42. *     fix bug in restoring "parent" address in RELEASE PSP.               *
  43. *   Version 2.2 3/3/87                                                    *
  44. *     add option to revector 8259 interrupt controller.                   *
  45. *       (thanks to Steve Glynn for this code)                             *
  46. *     add option to leave mark in place when RELEASE is run.              *
  47. *     restore save areas for EGA and interapplication communications      *
  48. *                                                                         *
  49. ***************************************************************************
  50. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  51. *   requires Turbo version 3 to compile.                                  *
  52. *   Compile with mAx dynamic memory = FFFF.                               *
  53. ***************************************************************************}
  54.  
  55. {$P128}
  56. {$C-}
  57.  
  58. program ReleaseTSR;
  59.   {-release system memory above the last mark call}
  60.   {-release expanded memory blocks allocated since the last mark call}
  61.  
  62. const
  63.   Version = '2.2';
  64.   ProtectChar = '!';          {marks whose name begins with this will be
  65.                               released ONLY if an exact name match occurs}
  66.   MaxBlocks = 128;            {max number of DOS allocation blocks supported}
  67.   MaxHandles = 32;            {max number of EMS allocation blocks supported}
  68.   EMSinterrupt = $67;         {the vector used by the expanded memory manager}
  69.  
  70.   MarkID = 'MARK PARAMETER BLOCK FOLLOWS'; {marking string for TSR MARK}
  71.   FmarkID = 'FMARK TSR';      {marking string for TSR FMARK}
  72.  
  73.   {offsets into resident copy of MARK.COM for data storage}
  74.   MarkOffset = $103;          {where markID is found in MARK TSR}
  75.   FmarkOffset = $60;          {where fmarkID is found in FMARK TSR}
  76.   VectorOffset = $120;        {where vector table is stored}
  77.   EGAsavOffset = $520;        {where the EGA save save is stored}
  78.   IntComOffset = $528;        {where the interapps comm area is stored}
  79.   EMScntOffset = $538;        {where count of EMS active pages is stored}
  80.   EMSmapOffset = $53A;        {where the page map is stored}
  81.  
  82. type
  83.   registers =
  84.   record case Integer of
  85.     1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  86.     2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  87.   end;
  88.  
  89.   HandlePageRecord =
  90.   record
  91.     handle : Integer;
  92.     numpages : Integer;
  93.   end;
  94.  
  95.   PageArray = array[1..MaxHandles] of HandlePageRecord;
  96.   PageArrayPtr = ^PageArray;
  97.  
  98.   Block =
  99.   record                      {store info about each memory block}
  100.     mcb : Integer;
  101.     psp : Integer;
  102.     releaseIt : Boolean;
  103.   end;
  104.  
  105.   BlockType = 0..MaxBlocks;
  106.   BlockArray = array[BlockType] of Block;
  107.   AllStrings = string[255];
  108.   HexString = string[4];
  109.  
  110. var
  111.   Blocks : BlockArray;
  112.   bottomBlock, blockNum : BlockType;
  113.   markName : AllStrings;
  114.   Regs : registers;
  115.   FilMarkHandles, ReturnCode, StartMCB, StoredHandles, EMShandles : Integer;
  116.   FilMarkPageMap, Map, StoredMap : PageArrayPtr;
  117.   TrappedBytes : Real;
  118.   Debug, Revector8259, KeepMark, MemMark, FilMark : Boolean;
  119.   Vectors : array[0..1023] of Byte;
  120.   EGAsavTable : array[0..7] of byte;
  121.   IntComTable : array[0..15] of byte;
  122.  
  123.   procedure Abort(msg : AllStrings);
  124.     {-halt in case of error}
  125.   begin
  126.     WriteLn(msg);
  127.     Halt(1);
  128.   end {Abort} ;
  129.  
  130.   procedure FindTheBlocks;
  131.     {-scan memory for the allocated memory blocks}
  132.   const
  133.     MidBlockID = $4D;         {byte DOS uses to identify part of MCB chain}
  134.     EndBlockID = $5A;         {byte DOS uses to identify last block of MCB chain}
  135.   var
  136.     mcbSeg : Integer;         {segment address of current MCB}
  137.     nextSeg : Integer;        {computed segment address for the next MCB}
  138.     gotFirst : Boolean;       {true after first MCB is found}
  139.     gotLast : Boolean;        {true after last MCB is found}
  140.     idbyte : Byte;            {byte that DOS uses to identify an MCB}
  141.  
  142.     function GetStartMCB : Integer;
  143.       {-return the first MCB segment}
  144.     begin
  145.       Regs.ah := $52;
  146.       MsDos(Regs);
  147.       GetStartMCB := MemW[Regs.es:(Regs.bx-2)];
  148.     end {getstartmcb} ;
  149.  
  150.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  151.                             var gotFirst, gotLast : Boolean);
  152.       {-store information regarding the memory block}
  153.     var
  154.       nextID : Byte;
  155.       pspAdd : Integer;       {segment address of the current PSP}
  156.       mcbLen : Integer;       {size of the current memory block in paragraphs}
  157.  
  158.     begin
  159.  
  160.       mcbLen := MemW[mcbSeg:3]; {size of the MCB in paragraphs}
  161.       nextSeg := Succ(mcbSeg+mcbLen); {where the next MCB should be}
  162.       pspAdd := MemW[mcbSeg:1]; {address of program segment prefix for MCB}
  163.       nextID := Mem[nextSeg:0];
  164.  
  165.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  166.         blockNum := Succ(blockNum);
  167.         gotFirst := True;
  168.         with Blocks[blockNum] do begin
  169.           mcb := mcbSeg;
  170.           psp := pspAdd;
  171.         end;
  172.       end;
  173.  
  174.     end {storetheblock} ;
  175.  
  176.   begin
  177.  
  178.     {initialize}
  179.     StartMCB := GetStartMCB;
  180.     mcbSeg := StartMCB;
  181.     gotFirst := False;
  182.     gotLast := False;
  183.     blockNum := 0;
  184.  
  185.     {scan all memory until the last block is found}
  186.     repeat
  187.       idbyte := Mem[mcbSeg:0];
  188.       if idbyte = MidBlockID then begin
  189.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  190.         if gotFirst then
  191.           mcbSeg := nextSeg
  192.         else
  193.           mcbSeg := Succ(mcbSeg);
  194.       end else if gotFirst and (idbyte = EndBlockID) then begin
  195.         gotLast := True;
  196.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  197.       end else
  198.         {start block was invalid}
  199.         Abort('Corrupted allocation chain or program error....');
  200.     until gotLast;
  201.  
  202.   end {findtheblocks} ;
  203.  
  204.   function StUpcase(s : AllStrings) : AllStrings;
  205.     {-return the uppercase string}
  206.   var
  207.     i : Byte;
  208.  
  209.   begin
  210.     for i := 1 to Length(s) do
  211.       s[i] := UpCase(s[i]);
  212.     StUpcase := s;
  213.   end {stupcase} ;
  214.  
  215.   function FindMark(markName : AllStrings) : Integer;
  216.     {-find the last memory block matching idstring at offset idoffset}
  217.   var
  218.     b : BlockType;
  219.  
  220.     function HasIDstring(segment : Integer;
  221.                          idString : AllStrings;
  222.                          idOffset : Integer) : Boolean;
  223.       {-return true if idstring is found at segment:idoffset}
  224.     var
  225.       tString : AllStrings;
  226.       len : Byte;
  227.  
  228.     begin
  229.       len := Length(idString);
  230.       tString[0] := Chr(len);
  231.       Move(Mem[segment:idOffset], tString[1], len);
  232.       HasIDstring := (tString = idString);
  233.     end {HasIDstring} ;
  234.  
  235.     function GetMarkName(segment : Integer) : AllStrings;
  236.       {-return a cleaned up mark name from the segment's PSP}
  237.     var
  238.       tString : AllStrings;
  239.       tlen : Byte absolute tString;
  240.  
  241.     begin
  242.       Move(Mem[segment:$80], tString[0], 128);
  243.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  244.         Delete(tString, 1, 1);
  245.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  246.         tlen := Pred(tlen);
  247.       GetMarkName := StUpcase(tString);
  248.     end;                      {GetMarkName}
  249.  
  250.     function MatchMemMark(segment : Integer;
  251.                           markName : AllStrings;
  252.                           var b : BlockType) : Boolean;
  253.       {-return true if MemMark is unnamed or matches current name}
  254.     var
  255.       tString : AllStrings;
  256.       FoundIt : Boolean;
  257.  
  258.     begin
  259.       {check the mark name stored in the PSP of the mark block}
  260.       tString := GetMarkName(segment);
  261.       if (markName <> '') then begin
  262.         FoundIt := (tString = StUpcase(markName));
  263.         if not(FoundIt) then
  264.           if (tString <> '') and (tString[1] = ProtectChar) then
  265.             {current mark is protected, stop searching}
  266.             b := 1;
  267.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  268.         {stored mark name is protected}
  269.         FoundIt := False;
  270.         {stop checking}
  271.         b := 1;
  272.       end else
  273.         {match any mark}
  274.         FoundIt := True;
  275.       if not(FoundIt) then
  276.         b := Pred(b);
  277.       MatchMemMark := FoundIt;
  278.     end {MatchMemMark} ;
  279.  
  280.     function MatchFilMark(segment : Integer;
  281.                           markName : AllStrings;
  282.                           var b : BlockType) : Boolean;
  283.       {-return true if FilMark is unnamed or matches current name}
  284.     var
  285.       tString : AllStrings;
  286.       FoundIt : Boolean;
  287.  
  288.       function ExistFile(path : AllStrings) : Boolean;
  289.         {-return true if file exists}
  290.       var
  291.         f : file;
  292.  
  293.       begin
  294.         Assign(f, path);
  295.         {$I-}
  296.         Reset(f);
  297.         {$I+}
  298.         ExistFile := (IOResult = 0);
  299.         Close(f);
  300.       end;                    {existfile}
  301.  
  302.     begin
  303.       {check the mark name stored in the PSP of the mark block}
  304.       tString := GetMarkName(segment);
  305.       if (markName <> '') then begin
  306.         markName := StUpcase(markName);
  307.         FoundIt := (tString = markName);
  308.         if FoundIt then begin
  309.           {Assure named file exists}
  310.           WriteLn('Finding mark file: ', markName);
  311.           FoundIt := ExistFile(markName);
  312.           if not(FoundIt) then
  313.             {stop checking}
  314.             b := 1;
  315.         end;
  316.       end else
  317.         {file marks must be named on RELEASE command line}
  318.         FoundIt := False;
  319.       if not(FoundIt) then
  320.         b := Pred(b);
  321.       MatchFilMark := FoundIt;
  322.     end {MatchFilMark} ;
  323.  
  324.   begin
  325.     {scan from the last block down to find the last MARK TSR}
  326.     b := blockNum;
  327.     MemMark := False;
  328.     FilMark := False;
  329.     repeat
  330.       if Blocks[b].psp = CSeg then
  331.         {assure this program's command line is not matched}
  332.         b := Pred(b)
  333.       else if HasIDstring(Blocks[b].psp, markID, markOffset) then
  334.         {an in-memory mark}
  335.         MemMark := MatchMemMark(Blocks[b].psp, markName, b)
  336.       else if HasIDstring(Blocks[b].psp, fmarkID, fmarkOffset) then
  337.         {a file mark}
  338.         FilMark := MatchFilMark(Blocks[b].psp, markName, b)
  339.       else
  340.         {not a mark}
  341.         b := Pred(b);
  342.     until (b < 1) or MemMark or FilMark;
  343.     if not(MemMark or FilMark) then
  344.       Abort('No matching marker found, or protected marker encountered.');
  345.     FindMark := b;
  346.   end {findmark} ;
  347.  
  348.   function Hex(i : Integer) : HexString;
  349.     {-return hex representation of integer}
  350.   const
  351.     hc : array[0..15] of Char = '0123456789ABCDEF';
  352.   var
  353.     l, h : Byte;
  354.  
  355.   begin
  356.     l := Lo(i); h := Hi(i);
  357.     Hex := hc[h shr 4]+hc[h and $F]+hc[l shr 4]+hc[l and $F];
  358.   end {hex} ;
  359.  
  360.   procedure ReadMarkFile(markName : AllStrings);
  361.     {-read the mark file info into memory}
  362.   var
  363.     f : file;
  364.  
  365.   begin
  366.     Assign(f, markName);
  367.     Reset(f, 1);
  368.  
  369.     {read the vector table from the mark file, into a temporary memory area}
  370.     BlockRead(f, Vectors, 1024);
  371.  
  372.     {read the BIOS miscellaneous save areas into temporary tables}
  373.     blockread(f, EGAsavTable, 8);
  374.     blockread(f, IntComTable, 16);
  375.  
  376.     {read the number of EMS handles stored}
  377.     BlockRead(f, FilMarkHandles, 2);
  378.  
  379.     {get a page map area and read the page map into it}
  380.     GetMem(FilMarkPageMap, 4*FilMarkHandles);
  381.     BlockRead(f, FilMarkPageMap^, 4*FilMarkHandles);
  382.     Close(f);
  383.  
  384.     if not(keepmark) then
  385.       {delete the mark file so it causes no mischief later}
  386.       Erase(f);
  387.   end {ReadMarkFile} ;
  388.  
  389.   procedure CopyVectors(bottomBlock : BlockType);
  390.     {-put interrupt vectors back into table}
  391.   var
  392.     bottompsp:integer;
  393.  
  394.     procedure Reset8259;
  395.       {-Reset the 8259 interrupt controller to its powerup state}
  396.       {-Interrupts assumed OFF prior to calling this routine}
  397.  
  398.       function ATmachine : Boolean;
  399.         {-return true if machine is AT class}
  400.       var
  401.         machtype : Byte absolute $FFFF : $000E;
  402.  
  403.       begin
  404.         ATmachine := (machtype = $FC);
  405.       end {ATmachine} ;
  406.  
  407.       procedure Reset8259PC;
  408.         {-Reset the 8259 on a PC class machine}
  409.  
  410.       begin
  411.         inline(
  412.           $E4/$21/            { in      al,$21}
  413.           $88/$C4/            { mov     ah,al}
  414.           $B0/$13/            { mov     al,+$13}
  415.           $E6/$20/            { out     $20,al}
  416.           $B0/$08/            { mov     al,+$08}
  417.           $E6/$21/            { out     $21,al}
  418.           $B0/$09/            { mov     al,+$09}
  419.           $E6/$21/            { out     $21,al}
  420.           $88/$E0/            { mov     al,ah}
  421.           $E6/$21             { out     $21,al}
  422.           );
  423.       end {Reset8259PC} ;
  424.  
  425.       procedure Reset8259AT;
  426.         {-Reset the 8259 interrupt controllers on an AT machine}
  427.  
  428.       begin
  429.         inline(
  430.           $32/$C0/            { xor       al,al }
  431.           $E6/$F1/            { out       0f1h,al         ; Switch off an 80287 if necessary}
  432.           {Set up master 8259 }
  433.           $E4/$21/            { in        al,21h          ; Get current interrupt mask }
  434.           $8A/$E0/            { mov       ah,al           ; save it }
  435.           $B0/$11/            { mov       al,11h }
  436.           $E6/$20/            { out       20h,al }
  437.           $EB/$00/            { jmp       short $+2 }
  438.           $B0/$08/            { mov       al,8            ; Set up main interrupt vector number}
  439.           $E6/$21/            { out       21h,al }
  440.           $EB/$00/            { jmp       short $+2 }
  441.           $B0/$04/            { mov       al,4 }
  442.           $E6/$21/            { out       21h,al }
  443.           $EB/$00/            { jmp       short $+2 }
  444.           $B0/$01/            { mov       al,1 }
  445.           $E6/$21/            { out       21h,al }
  446.           $EB/$00/            { jmp       short $+2 }
  447.           $8A/$C4/            { mov       al,ah }
  448.           $E6/$21/            { out       21h,al }
  449.           {Set up slave 8259 }
  450.           $E4/$A1/            { in        al,0a1h         ; Get current interrupt mask }
  451.           $8A/$E0/            { mov       ah,al           ; save it }
  452.           $B0/$11/            { mov       al,11h }
  453.           $E6/$A0/            { out       0a0h,al }
  454.           $EB/$00/            { jmp       short $+2 }
  455.           $B0/$70/            { mov       al,70h }
  456.           $E6/$A1/            { out       0a1h,al }
  457.           $B0/$02/            { mov       al,2 }
  458.           $EB/$00/            { jmp       short $+2 }
  459.           $E6/$A1/            { out       0a1h,al }
  460.           $EB/$00/            { jmp       short $+2 }
  461.           $B0/$01/            { mov       al,1 }
  462.           $E6/$A1/            { out       0a1h,al }
  463.           $EB/$00/            { jmp       short $+2 }
  464.           $8A/$C4/            { mov       al,ah           ; Reset previous interrupt state }
  465.           $E6/$A1             { out       0a1h,al }
  466.           );
  467.       end {Reset8259AT} ;
  468.  
  469.     begin
  470.       if ATmachine then
  471.         Reset8259AT
  472.       else
  473.         Reset8259PC;
  474.     end {Reset8259} ;
  475.  
  476.   begin
  477.     {interrupts off}
  478.     inline($FA);
  479.  
  480.     {restore the main interrupt vector table and the misc save areas}
  481.     if FilMark then begin
  482.       Move(Vectors, Mem[0:0], 1024);
  483.       Move(EGAsavTable, Mem[$40:$A8], 8);
  484.       Move(IntComTable, Mem[$40:$F0], 16);
  485.     end else begin
  486.       bottompsp:=Blocks[bottomBlock].psp;
  487.       Move(Mem[bottompsp:vectorOffset], Mem[0:0], 1024);
  488.       Move(Mem[bottompsp:EGAsavOffset], Mem[$40:$A8], 8);
  489.       Move(Mem[bottompsp:IntComOffset], Mem[$40:$F0], 16);
  490.     end;
  491.  
  492.     {move the old termination/break/error addresses into this program}
  493.     Move(Mem[0:$88], Mem[CSeg:$0A], 12);
  494.  
  495.     {restore the "parent address" used by the DOS EXIT command to remove a shell}
  496.     Move(Mem[CSeg:$0C], Mem[CSeg:$16], 2);
  497.  
  498.     {reset 8259 if requested}
  499.     if Revector8259 then
  500.       Reset8259;
  501.  
  502.     {interrupts on}
  503.     inline($FB);
  504.   end {CopyVectors} ;
  505.  
  506.   procedure MarkBlocks(bottomBlock : BlockType);
  507.     {-mark those blocks to be released}
  508.   var
  509.     b : BlockType;
  510.     commandPsp, markPsp : Integer;
  511.     ch : Char;
  512.  
  513.     function Cardinal(i : Integer) : Real;
  514.       {-return "unsigned integer" in range 0..65535}
  515.  
  516.     begin
  517.       if i < 0 then
  518.         Cardinal := i+65536.0
  519.       else
  520.         Cardinal := i;
  521.     end {Cardinal} ;
  522.  
  523.     procedure BatchWarning(b : BlockType);
  524.       {-warn about the trapping effect of batch files}
  525.     var
  526.       t : BlockType;
  527.     begin
  528.       WriteLn('Memory space for TSRs installed prior to batch file');
  529.       WriteLn('will not be released until batch file completes.');
  530.       WriteLn;
  531.       ReturnCode := 1;
  532.       {Accumulate number of bytes temporarily trapped}
  533.       for t := 1 to b do
  534.         if Blocks[t].releaseIt then
  535.           TrappedBytes := TrappedBytes+16.0*Cardinal(MemW[Blocks[t].mcb:3]);
  536.     end {BatchWarning} ;
  537.  
  538.   begin
  539.  
  540.     commandPsp := Blocks[2].psp;
  541.     markPsp := Blocks[bottomBlock].psp;
  542.  
  543.     for b := 1 to blockNum do
  544.       with Blocks[b] do
  545.         if (b < bottomBlock) then begin
  546.           {release any trapped environment block}
  547.           if KeepMark then
  548.             releaseIt := (psp <> CSeg) and (Cardinal(psp) > Cardinal(markPsp))
  549.           else
  550.             releaseIt := (psp <> CSeg) and (Cardinal(psp) >= Cardinal(markPsp));
  551.         end else if (psp = commandPsp) then begin
  552.           {Don't release blocks owned by COMMAND.COM}
  553.           releaseIt := False;
  554.           BatchWarning(b);
  555.         end else if KeepMark then
  556.           {release all but RELEASE and the mark}
  557.           releaseIt := (psp <> CSeg) and (psp <> markPsp)
  558.         else
  559.           {release all but RELEASE itself}
  560.           releaseIt := (psp <> CSeg);
  561.  
  562.     if debug then begin
  563.       for b := 1 to blockNum do with Blocks[b] do
  564.         WriteLn(b:3, ' ', Hex(psp), ' ', Hex(mcb), ' ', releaseIt);
  565.       Read(Kbd, ch);
  566.     end;
  567.  
  568.   end {MarkBlocks} ;
  569.  
  570.   procedure ReleaseMem;
  571.     {-release DOS memory marked for release}
  572.   var
  573.     b : BlockType;
  574.  
  575.   begin
  576.     with Regs do
  577.       for b := 1 to blockNum do
  578.         with Blocks[b] do
  579.           if releaseIt then begin
  580.             ah := $49;
  581.             {the block is always 1 paragraph above the MCB}
  582.             es := Succ(mcb);
  583.             MsDos(Regs);
  584.             if Odd(flags) then begin
  585.               WriteLn('Could not release block at segment ', Hex(es));
  586.               Abort('Memory may be a mess... Please reboot');
  587.             end;
  588.           end;
  589.   end {releasemem} ;
  590.  
  591.   function EMSpresent : Boolean;
  592.     {-return true if EMS memory manager is present}
  593.   var
  594.     f : file;
  595.  
  596.   begin
  597.     {"file handle" defined by the expanded memory manager at installation}
  598.     Assign(f, 'EMMXXXX0');
  599.     {$I-}
  600.     Reset(f);
  601.     {$I+}
  602.     EMSpresent := (IOResult = 0);
  603.     Close(f);
  604.   end {EMSpresent} ;
  605.  
  606.   procedure RestoreEMSmap;
  607.     {-restore EMS to state at time of mark}
  608.  
  609.     function EMShandlesActive : Integer;
  610.       {-return the number of active EMS handles}
  611.  
  612.     begin
  613.       Regs.ah := $4B;
  614.       Intr(EMSinterrupt, Regs);
  615.       if Regs.ah <> 0 then begin
  616.         WriteLn('EMS device not responding');
  617.         EMShandlesActive := 0;
  618.         Exit;
  619.       end;
  620.       EMShandlesActive := Regs.bx;
  621.     end {EMShandlesActive} ;
  622.  
  623.     function GetHandles(bottomBlock : BlockType; EMScntOffset : Integer) : Integer;
  624.       {-return the number of handles stored by mark}
  625.     var
  626.       gh : Integer;
  627.  
  628.     begin
  629.       if FilMark then
  630.         GetHandles := FilMarkHandles
  631.       else begin
  632.         Move(Mem[Blocks[bottomBlock].psp:EMScntOffset], gh, 2);
  633.         GetHandles := gh;
  634.       end;
  635.     end {gethandles} ;
  636.  
  637.     function GetStoredMap(bottomBlock : BlockType; EMSmapOffset : Integer) : PageArrayPtr;
  638.       {-returns a pointer to the stored page array}
  639.  
  640.     begin
  641.       if FilMark then
  642.         GetStoredMap := FilMarkPageMap
  643.       else
  644.         GetStoredMap := Ptr(Blocks[bottomBlock].psp, EMSmapOffset);
  645.     end {GetStoredMap} ;
  646.  
  647.     procedure EMSpageMap(var PageMap : PageArray);
  648.       {-return an array of the allocated memory blocks}
  649.  
  650.     begin
  651.       Regs.ah := $4D;
  652.       Regs.es := Seg(PageMap);
  653.       Regs.di := Ofs(PageMap);
  654.       Regs.bx := 0;
  655.       Intr(EMSinterrupt, Regs);
  656.       if Regs.ah <> 0 then
  657.         WriteLn('EMS device not responding');
  658.     end {EMSpageMap} ;
  659.  
  660.     procedure ReleaseEMSblocks(var oldmap, newmap : PageArray);
  661.       {-release those EMS blocks allocated since MARK was installed}
  662.     var
  663.       o, n, nhandle : Integer;
  664.  
  665.       procedure EMSdeallocate(EMShandle : Integer);
  666.         {-release the allocated expanded memory}
  667.  
  668.       begin
  669.         Regs.ah := $45;
  670.         Regs.dx := EMShandle;
  671.         Intr(EMSinterrupt, Regs);
  672.         if Regs.ah <> 0 then begin
  673.           WriteLn('Program error or EMS device not responding');
  674.           Abort('EMS memory may be a mess... Please reboot');
  675.         end;
  676.       end {EMSdeallocate} ;
  677.  
  678.     begin
  679.       for n := 1 to EMShandles do begin
  680.         {scan all current handles}
  681.         nhandle := newmap[n].handle;
  682.         if StoredHandles > 0 then begin
  683.           {see if current handle matches one stored by MARK}
  684.           o := 1;
  685.           while (oldmap[o].handle <> nhandle) and (o <= StoredHandles) do
  686.             o := Succ(o);
  687.           {if not, deallocate the current handle}
  688.           if (o > StoredHandles) then
  689.             EMSdeallocate(nhandle);
  690.         end else
  691.           {no handles stored by MARK, deallocate all current handles}
  692.           EMSdeallocate(nhandle);
  693.       end;
  694.     end {releaseEMSblocks} ;
  695.  
  696.   begin
  697.     {see how many EMS handles are currently active}
  698.     EMShandles := EMShandlesActive;
  699.     if EMShandles > MaxHandles then
  700.       WriteLn('EMS process count exceeds capacity of RELEASE - no action taken')
  701.     else if EMShandles <> 0 then begin
  702.       {see how many handles were active when MARK was installed}
  703.       StoredHandles := GetHandles(bottomBlock, EMScntOffset);
  704.       {get the existing EMS page map}
  705.       GetMem(Map, 4*EMShandles);
  706.       EMSpageMap(Map^);
  707.       {get the stored page map}
  708.       StoredMap := GetStoredMap(bottomBlock, EMSmapOffset);
  709.       {compare the two maps and deallocate pages not in the stored map}
  710.       ReleaseEMSblocks(StoredMap^, Map^);
  711.     end;
  712.   end {RestoreEMSmap} ;
  713.  
  714.   procedure GetOptions;
  715.     {-Analyze command line for options}
  716.   var
  717.     arg : AllStrings;
  718.     arglen : Byte absolute arg;
  719.     i : Integer;
  720.  
  721.     procedure WriteHelp;
  722.       {-Show the options}
  723.     begin
  724.       WriteLn('RELEASE ', Version, ', by TurboPower Software');
  725.       WriteLn('====================================================');
  726.       WriteLn('RELEASE removes memory-resident programs from memory');
  727.       WriteLn('and restores the interrupt vectors to their state as');
  728.       WriteLn('found prior to the installation of a MARK.');
  729.       WriteLn('RELEASE manages both normal DOS memory and also');
  730.       WriteLn('Lotus/Intel Expanded Memory.');
  731.       WriteLn;
  732.       WriteLn('RELEASE accepts the following command line syntax:');
  733.       WriteLn;
  734.       WriteLn('  RELEASE [Options] [MarkName]');
  735.       WriteLn;
  736.       WriteLn('Options may be preceded by either / or -. Valid options');
  737.       WriteLn('are as follows:');
  738.       WriteLn;
  739.       WriteLn('     /K     Release memory, but Keep the mark in place.');
  740.       WriteLn('     /R     Revector the 8259 interrupt controller to its');
  741.       WriteLn('            powerup state.');
  742.       WriteLn('     /?     Write this help screen.');
  743.       Halt(1);
  744.     end {WriteHelp} ;
  745.  
  746.   begin
  747.  
  748.     WriteLn;
  749.  
  750.     {Initialize defaults}
  751.     markName := '';
  752.     Revector8259 := False;
  753.     KeepMark := False;
  754.     ReturnCode := 0;
  755.     TrappedBytes := 0.0;
  756.     Debug := false;
  757.  
  758.     i := 1;
  759.     while i <= ParamCount do begin
  760.       arg := ParamStr(i);
  761.       if (arg[1] = '?') then
  762.         WriteHelp
  763.       else if (arg[1] = '-') or (arg[1] = '/') then
  764.         case arglen of
  765.           1 : Abort('Missing command option following '+arg);
  766.           2 : case UpCase(arg[2]) of
  767.                 '?' : WriteHelp;
  768.                 'R' : Revector8259 := True;
  769.                 'K' : KeepMark := True;
  770.                 'D' : Debug := True;
  771.               else
  772.                 Abort('Unknown command option: '+arg);
  773.               end;
  774.         else
  775.           Abort('Unknown command option: '+arg);
  776.         end
  777.       else
  778.         {named mark}
  779.         markName := arg;
  780.       i := Succ(i);
  781.     end;
  782.   end {getOptions} ;
  783.  
  784. begin
  785.  
  786.   {analyze command line for options}
  787.   GetOptions;
  788.  
  789.   {get all allocated memory blocks in normal memory}
  790.   FindTheBlocks;
  791.  
  792.   {find the last one marked with the MARK idstring, and MarkName if specified}
  793.   bottomBlock := FindMark(markName);
  794.  
  795.   {mark those blocks to be released}
  796.   MarkBlocks(bottomBlock);
  797.  
  798.   {get file mark information into memory}
  799.   if FilMark then
  800.     ReadMarkFile(markName);
  801.  
  802.   {copy the vector table from the MARK copy}
  803.   CopyVectors(bottomBlock);
  804.  
  805.   {release normal memory marked for release}
  806.   ReleaseMem;
  807.  
  808.   {deal with expanded memory}
  809.   if EMSpresent then
  810.     RestoreEMSmap;
  811.  
  812.   {DOS will release this program's memory when it exits}
  813.   {write success message}
  814.   Write('RELEASE ', Version, ' - Memory released above last MARK ');
  815.   if markName <> '' then
  816.     Write('(', StUpcase(markName), ')');
  817.   WriteLn;
  818.  
  819.   if ReturnCode <> 0 then
  820.     WriteLn(TrappedBytes:0:0, ' bytes temporarily trapped until batch file completes');
  821.  
  822.   Halt(ReturnCode);
  823. end.
  824.